/*
 * Misc utility routines for accessing PMU corerev specific features
 * of the SiliconBackplane-based Broadcom chips.
 *
 * Copyright (C) 1999-2019, Broadcom.
 *
 *      Unless you and Broadcom execute a separate written software license
 * agreement governing use of this software, this software is licensed to you
 * under the terms of the GNU General Public License version 2 (the "GPL"),
 * available at http://www.broadcom.com/licenses/GPLv2.php, with the
 * following added to such license:
 *
 *      As a special exception, the copyright holders of this software give you
 * permission to link this software with independent modules, and to copy and
 * distribute the resulting executable under terms of your choice, provided that
 * you also meet, for each linked independent module, the terms and conditions
 * of the license of that module.  An independent module is a module which is
 * not derived from this software.  The special exception does not apply to any
 * modifications of the software.
 *
 *      Notwithstanding the above, under no circumstances may you combine this
 * software in any way with any other Broadcom software provided under a license
 * other than the GPL, without Broadcom's express prior written consent.
 *
 *
 * <<Broadcom-WL-IPTag/Open:>>
 *
 * $Id: hndpmu.c 783841 2018-10-09 06:24:16Z $
 */

/**
 * @file
 * Note: this file contains PLL/FLL related functions. A chip can contain
 * multiple PLLs/FLLs. However, in the context of this file the baseband ('BB')
 * PLL/FLL is referred to.
 *
 * Throughout this code, the prefixes 'pmu1_' and 'pmu2_' are used.
 * They refer to different revisions of the PMU (which is at revision 18 @ Apr
 * 25, 2012) pmu1_ marks the transition from PLL to ADFLL (Digital Frequency
 * Locked Loop). It supports fractional frequency generation. pmu2_ does not
 * support fractional frequency generation.
 */

#include <bcm_cfg.h>
#include <typedefs.h>
#include <bcmdefs.h>
#include <osl.h>
#include <bcmutils.h>
#include <siutils.h>
#include <bcmdevs.h>
#include <hndsoc.h>
#include <sbchipc.h>
#include <hndchipc.h>
#include <hndpmu.h>
#include <hndlhl.h>
#if defined(BCMULP)
#include <ulp.h>
#endif /* defined(BCMULP) */
#include <sbgci.h>
#ifdef EVENT_LOG_COMPILE
#include <event_log.h>
#endif // endif
#include <sbgci.h>
#include <lpflags.h>

#define PMU_ERROR(args)

#define PMU_MSG(args)

/* To check in verbose debugging messages not intended
 * to be on except on private builds.
 */
#define PMU_NONE(args)
#define flags_shift 14

/** contains resource bit positions for a specific chip */
struct rsc_per_chip_s {
    uint8 ht_avail;
    uint8 macphy_clkavail;
    uint8 ht_start;
    uint8 otp_pu;
    uint8 macphy_aux_clkavail;
};

typedef struct rsc_per_chip_s rsc_per_chip_t;

#if defined(BCMPMU_STATS) && !defined(BCMPMU_STATS_DISABLED)
bool _pmustatsenab = TRUE;
#else
bool _pmustatsenab = FALSE;
#endif /* BCMPMU_STATS */

/**
 * Balance between stable SDIO operation and power consumption is achieved using
 * this function. Note that each drive strength table is for a specific VDDIO of
 * the SDIO pads, ideally this function should read the VDDIO itself to select
 * the correct table. For now it has been solved with the 'BCM_SDIO_VDDIO'
 * preprocessor constant.
 *
 * 'drivestrength': desired pad drive strength in mA. Drive strength of 0
 * requests tri-state (if hardware supports this), if no hw support drive
 * strength is not programmed.
 */
void si_sdiod_drive_strength_init(si_t *sih, osl_t *osh, uint32 drivestrength)
{
    /*
     * Note:
     * This function used to set the SDIO drive strength via PMU_CHIPCTL1 for
     * the 43143, 4330, 4334, 4336, 43362 chips.  These chips are now no longer
     * supported, so the code has been deleted. Newer chips have the SDIO drive
     * strength setting via a GCI Chip Control register, but the bit definitions
     * are chip-specific.  We are keeping this function available (accessed via
     * DHD 'sdiod_drive' IOVar) in case these newer chips need to provide
     * access.
     */
    UNUSED_PARAMETER(sih);
    UNUSED_PARAMETER(osh);
    UNUSED_PARAMETER(drivestrength);
}

void si_switch_pmu_dependency(si_t *sih, uint mode)
{
#ifdef DUAL_PMU_SEQUENCE
    osl_t *osh = si_osh(sih);
    uint32 current_res_state;
    uint32 min_mask, max_mask;
    const pmu_res_depend_t *pmu_res_depend_table = NULL;
    uint pmu_res_depend_table_sz = 0;
    uint origidx;
    pmuregs_t *pmu;
    chipcregs_t *cc;
    BCM_REFERENCE(cc);

    origidx = si_coreidx(sih);
    if (AOB_ENAB(sih)) {
        pmu = si_setcore(sih, PMU_CORE_ID, 0);
        cc = si_setcore(sih, CC_CORE_ID, 0);
    } else {
        pmu = si_setcoreidx(sih, SI_CC_IDX);
        cc = si_setcoreidx(sih, SI_CC_IDX);
    }
    ASSERT(pmu != NULL);

    current_res_state = R_REG(osh, &pmu->res_state);
    min_mask = R_REG(osh, &pmu->min_res_mask);
    max_mask = R_REG(osh, &pmu->max_res_mask);
    W_REG(osh, &pmu->min_res_mask, (min_mask | current_res_state));
    switch (mode) {
        case PMU_4364_1x1_MODE: {
            if (CHIPID(sih->chip) == BCM4364_CHIP_ID) {
                pmu_res_depend_table = bcm4364a0_res_depend_1x1;
                pmu_res_depend_table_sz = ARRAYSIZE(bcm4364a0_res_depend_1x1);
                max_mask = PMU_4364_MAX_MASK_1x1;
                W_REG(osh, &pmu->res_table_sel, RES4364_SR_SAVE_RESTORE);
                W_REG(osh, &pmu->res_updn_timer,
                      PMU_4364_SAVE_RESTORE_UPDNTIME_1x1);
#if defined(SAVERESTORE)
                if (SR_ENAB()) {
                    /* Disable 3x3 SR engine */
                    W_REG(osh, &cc->sr1_control0,
                          CC_SR0_4364_SR_ENG_CLK_EN |
                              CC_SR0_4364_SR_RSRC_TRIGGER |
                              CC_SR0_4364_SR_WD_MEM_MIN_DIV |
                              CC_SR0_4364_SR_INVERT_CLK |
                              CC_SR0_4364_SR_ENABLE_HT |
                              CC_SR0_4364_SR_ALLOW_PIC |
                              CC_SR0_4364_SR_PMU_MEM_DISABLE);
                }
#endif /* SAVERESTORE */
            }
            break;
        }
        case PMU_4364_3x3_MODE: {
            if (CHIPID(sih->chip) == BCM4364_CHIP_ID) {
                W_REG(osh, &pmu->res_table_sel, RES4364_SR_SAVE_RESTORE);
                W_REG(osh, &pmu->res_updn_timer,
                      PMU_4364_SAVE_RESTORE_UPDNTIME_3x3);
                /* Change the dependency table only if required */
                if ((max_mask != PMU_4364_MAX_MASK_3x3) ||
                    (max_mask != PMU_4364_MAX_MASK_RSDB)) {
                    pmu_res_depend_table = bcm4364a0_res_depend_rsdb;
                    pmu_res_depend_table_sz =
                        ARRAYSIZE(bcm4364a0_res_depend_rsdb);
                    max_mask = PMU_4364_MAX_MASK_3x3;
                }
#if defined(SAVERESTORE)
                if (SR_ENAB()) {
                    /* Enable 3x3 SR engine */
                    W_REG(osh, &cc->sr1_control0,
                          CC_SR0_4364_SR_ENG_CLK_EN |
                              CC_SR0_4364_SR_RSRC_TRIGGER |
                              CC_SR0_4364_SR_WD_MEM_MIN_DIV |
                              CC_SR0_4364_SR_INVERT_CLK |
                              CC_SR0_4364_SR_ENABLE_HT |
                              CC_SR0_4364_SR_ALLOW_PIC |
                              CC_SR0_4364_SR_PMU_MEM_DISABLE |
                              CC_SR0_4364_SR_ENG_EN_MASK);
                }
#endif /* SAVERESTORE */
            }
            break;
        }
        case PMU_4364_RSDB_MODE:
        default: {
            if (CHIPID(sih->chip) == BCM4364_CHIP_ID) {
                W_REG(osh, &pmu->res_table_sel, RES4364_SR_SAVE_RESTORE);
                W_REG(osh, &pmu->res_updn_timer,
                      PMU_4364_SAVE_RESTORE_UPDNTIME_3x3);
                /* Change the dependency table only if required */
                if ((max_mask != PMU_4364_MAX_MASK_3x3) ||
                    (max_mask != PMU_4364_MAX_MASK_RSDB)) {
                    pmu_res_depend_table = bcm4364a0_res_depend_rsdb;
                    pmu_res_depend_table_sz =
                        ARRAYSIZE(bcm4364a0_res_depend_rsdb);
                    max_mask = PMU_4364_MAX_MASK_RSDB;
                }
#if defined(SAVERESTORE)
                if (SR_ENAB()) {
                    /* Enable 3x3 SR engine */
                    W_REG(osh, &cc->sr1_control0,
                          CC_SR0_4364_SR_ENG_CLK_EN |
                              CC_SR0_4364_SR_RSRC_TRIGGER |
                              CC_SR0_4364_SR_WD_MEM_MIN_DIV |
                              CC_SR0_4364_SR_INVERT_CLK |
                              CC_SR0_4364_SR_ENABLE_HT |
                              CC_SR0_4364_SR_ALLOW_PIC |
                              CC_SR0_4364_SR_PMU_MEM_DISABLE |
                              CC_SR0_4364_SR_ENG_EN_MASK);
                }
#endif /* SAVERESTORE */
            }
            break;
        }
    }
    si_pmu_resdeptbl_upd(sih, osh, pmu, pmu_res_depend_table,
                         pmu_res_depend_table_sz);
    W_REG(osh, &pmu->max_res_mask, max_mask);
    W_REG(osh, &pmu->min_res_mask, min_mask);
    si_pmu_wait_for_steady_state(sih, osh, pmu);
    /* Add some delay; allow resources to come up and settle. */
    OSL_DELAY(0xC8);
    si_setcoreidx(sih, origidx);
#endif /* DUAL_PMU_SEQUENCE */
}

#if defined(BCMULP)

int si_pmu_ulp_register(si_t *sih)
{
    return ulp_p1_module_register(ULP_MODULE_ID_PMU, &ulp_pmu_ctx, (void *)sih);
}

static uint si_pmu_ulp_get_retention_size_cb(void *handle,
                                             ulp_ext_info_t *einfo)
{
    ULP_DBG(("%s: sz: %d\n", __FUNCTION__, sizeof(si_pmu_ulp_cr_dat_t)));
    return sizeof(si_pmu_ulp_cr_dat_t);
}

static int si_pmu_ulp_enter_cb(void *handle, ulp_ext_info_t *einfo,
                               uint8 *cache_data)
{
    si_pmu_ulp_cr_dat_t crinfo = {0};
    crinfo.ilpcycles_per_sec = ilpcycles_per_sec;
    ULP_DBG(("%s: ilpcycles_per_sec: %x\n", __FUNCTION__, ilpcycles_per_sec));
    memcpy(cache_data, (void *)&crinfo, sizeof(crinfo));
    return BCME_OK;
}

static int si_pmu_ulp_exit_cb(void *handle, uint8 *cache_data,
                              uint8 *p2_cache_data)
{
    si_pmu_ulp_cr_dat_t *crinfo = (si_pmu_ulp_cr_dat_t *)cache_data;

    ilpcycles_per_sec = crinfo->ilpcycles_per_sec;
    ULP_DBG(("%s: ilpcycles_per_sec: %x, cache_data: %p\n", __FUNCTION__,
             ilpcycles_per_sec, cache_data));
    return BCME_OK;
}

void si_pmu_ulp_chipconfig(si_t *sih, osl_t *osh)
{
    uint32 reg_val;

    BCM_REFERENCE(reg_val);

    if (CHIPID(sih->chip) == BCM43012_CHIP_ID) {
        /* DS1 reset and clk enable init value config */
        si_pmu_chipcontrol(sih, PMU_CHIPCTL14, ~0x0,
                           (PMUCCTL14_43012_ARMCM3_RESET_INITVAL |
                            PMUCCTL14_43012_DOT11MAC_CLKEN_INITVAL |
                            PMUCCTL14_43012_SDIOD_RESET_INIVAL |
                            PMUCCTL14_43012_SDIO_CLK_DMN_RESET_INITVAL |
                            PMUCCTL14_43012_SOCRAM_CLKEN_INITVAL |
                            PMUCCTL14_43012_M2MDMA_RESET_INITVAL |
                            PMUCCTL14_43012_DOT11MAC_PHY_CLK_EN_INITVAL |
                            PMUCCTL14_43012_DOT11MAC_PHY_CNTL_EN_INITVAL));

        /* Clear SFlash clock request and enable High Quality clock */
        CHIPC_REG(sih, clk_ctl_st, CCS_SFLASH_CLKREQ | CCS_HQCLKREQ,
                  CCS_HQCLKREQ);

        reg_val = PMU_REG(sih, min_res_mask, ~0x0, ULP_MIN_RES_MASK);
        ULP_DBG(("si_pmu_ulp_chipconfig: min_res_mask: 0x%08x\n", reg_val));

        /* Force power switch off */
        si_pmu_chipcontrol(sih, PMU_CHIPCTL2,
                           (PMUCCTL02_43012_SUBCORE_PWRSW_FORCE_ON |
                            PMUCCTL02_43012_PHY_PWRSW_FORCE_ON),
                           0);
    }
}

void si_pmu_ulp_ilp_config(si_t *sih, osl_t *osh, uint32 ilp_period)
{
    pmuregs_t *pmu;
    pmu = si_setcoreidx(sih, si_findcoreidx(sih, PMU_CORE_ID, 0));
    W_REG(osh, &pmu->ILPPeriod, ilp_period);
    si_lhl_ilp_config(sih, osh, ilp_period);
}

/** Initialize DS1 PMU hardware resources */
void si_pmu_ds1_res_init(si_t *sih, osl_t *osh)
{
    pmuregs_t *pmu;
    uint origidx;
    const pmu_res_updown_t *pmu_res_updown_table = NULL;
    uint pmu_res_updown_table_sz = 0;

    /* Remember original core before switch to chipc/pmu */
    origidx = si_coreidx(sih);
    if (AOB_ENAB(sih)) {
        pmu = si_setcore(sih, PMU_CORE_ID, 0);
    } else {
        pmu = si_setcoreidx(sih, SI_CC_IDX);
    }
    ASSERT(pmu != NULL);

    switch (CHIPID(sih->chip)) {
        case BCM43012_CHIP_ID:
            pmu_res_updown_table = bcm43012a0_res_updown_ds1;
            pmu_res_updown_table_sz = ARRAYSIZE(bcm43012a0_res_updown_ds1);
            break;

        default:
            break;
    }

    /* Program up/down timers */
    while (pmu_res_updown_table_sz--) {
        ASSERT(pmu_res_updown_table != NULL);
        PMU_MSG(("DS1: Changing rsrc %d res_updn_timer to 0x%x\n",
                 pmu_res_updown_table[pmu_res_updown_table_sz].resnum,
                 pmu_res_updown_table[pmu_res_updown_table_sz].updown));
        W_REG(osh, &pmu->res_table_sel,
              pmu_res_updown_table[pmu_res_updown_table_sz].resnum);
        W_REG(osh, &pmu->res_updn_timer,
              pmu_res_updown_table[pmu_res_updown_table_sz].updown);
    }

    /* Return to original core */
    si_setcoreidx(sih, origidx);
}

#endif /* defined(BCMULP) */

uint32 si_pmu_wake_bit_offset(si_t *sih)
{
    uint32 wakebit;

    switch (CHIPID(sih->chip)) {
        case BCM4347_CHIP_GRPID:
            wakebit = CC2_4347_GCI2WAKE_MASK;
            break;
        default:
            wakebit = 0;
            ASSERT(0);
            break;
    }

    return wakebit;
}

void si_pmu_set_min_res_mask(si_t *sih, osl_t *osh, uint min_res_mask)
{
    pmuregs_t *pmu;
    uint origidx;

    /* Remember original core before switch to chipc/pmu */
    origidx = si_coreidx(sih);
    if (AOB_ENAB(sih)) {
        pmu = si_setcore(sih, PMU_CORE_ID, 0);
    } else {
        pmu = si_setcoreidx(sih, SI_CC_IDX);
    }
    ASSERT(pmu != NULL);

    W_REG(osh, &pmu->min_res_mask, min_res_mask);
    OSL_DELAY(0x64);

    /* Return to original core */
    si_setcoreidx(sih, origidx);
}

bool si_pmu_cap_fast_lpo(si_t *sih)
{
    return (PMU_REG(sih, core_cap_ext, 0, 0) & PCAP_EXT_USE_MUXED_ILP_CLK_MASK)
               ? TRUE
               : FALSE;
}

int si_pmu_fast_lpo_disable(si_t *sih)
{
    if (!si_pmu_cap_fast_lpo(sih)) {
        PMU_ERROR(("%s: No Fast LPO capability\n", __FUNCTION__));
        return BCME_ERROR;
    }

    PMU_REG(sih, pmucontrol_ext,
            PCTL_EXT_FASTLPO_ENAB | PCTL_EXT_FASTLPO_SWENAB |
                PCTL_EXT_FASTLPO_PCIE_SWENAB,
            0);
    OSL_DELAY(0x3E8);
    return BCME_OK;
}

#ifdef BCMPMU_STATS
/*
 * 8 pmu statistics timer default map
 *
 * for CORE_RDY_AUX measure, set as below for timer 6 and 7 instead of
 * CORE_RDY_MAIN.
 *	//core-n active duration : pmu_rsrc_state(CORE_RDY_AUX)
 *	{ SRC_CORE_RDY_AUX, FALSE, TRUE, LEVEL_HIGH},
 *	//core-n active duration : pmu_rsrc_state(CORE_RDY_AUX)
 *	{ SRC_CORE_RDY_AUX, FALSE, TRUE, EDGE_RISE}
 */
static pmu_stats_timer_t pmustatstimer[] = {
    {SRC_LINK_IN_L12, FALSE, TRUE, PMU_STATS_LEVEL_HIGH}, // link_in_l12
    {SRC_LINK_IN_L23, FALSE, TRUE, PMU_STATS_LEVEL_HIGH}, // link_in_l23
    {SRC_PM_ST_IN_D0, FALSE, TRUE, PMU_STATS_LEVEL_HIGH}, // pm_st_in_d0
    {SRC_PM_ST_IN_D3, FALSE, TRUE, PMU_STATS_LEVEL_HIGH}, // pm_st_in_d3
    // deep-sleep duration : pmu_rsrc_state(XTAL_PU)
    {SRC_XTAL_PU, FALSE, TRUE, PMU_STATS_LEVEL_LOW},
    // deep-sleep entry count : pmu_rsrc_state(XTAL_PU)
    {SRC_XTAL_PU, FALSE, TRUE, PMU_STATS_EDGE_FALL},
    // core-n active duration : pmu_rsrc_state(CORE_RDY_MAIN)
    {SRC_CORE_RDY_MAIN, FALSE, TRUE, PMU_STATS_LEVEL_HIGH},
    // core-n active duration : pmu_rsrc_state(CORE_RDY_MAIN)
    {SRC_CORE_RDY_MAIN, FALSE, TRUE, PMU_STATS_EDGE_RISE}};

static void si_pmustatstimer_update(osl_t *osh, pmuregs_t *pmu, uint8 timerid)
{
    uint32 stats_timer_ctrl;

    W_REG(osh, &pmu->pmu_statstimer_addr, timerid);
    stats_timer_ctrl =
        ((pmustatstimer[timerid].src_num << PMU_ST_SRC_SHIFT) &
         PMU_ST_SRC_MASK) |
        ((pmustatstimer[timerid].cnt_mode << PMU_ST_CNT_MODE_SHIFT) &
         PMU_ST_CNT_MODE_MASK) |
        ((pmustatstimer[timerid].enable << PMU_ST_EN_SHIFT) & PMU_ST_EN_MASK) |
        ((pmustatstimer[timerid].int_enable << PMU_ST_INT_EN_SHIFT) &
         PMU_ST_INT_EN_MASK);
    W_REG(osh, &pmu->pmu_statstimer_ctrl, stats_timer_ctrl);
    W_REG(osh, &pmu->pmu_statstimer_N, 0);
}

void si_pmustatstimer_int_enable(si_t *sih)
{
    pmuregs_t *pmu;
    uint origidx;
    osl_t *osh = si_osh(sih);

    /* Remember original core before switch to chipc/pmu */
    origidx = si_coreidx(sih);
    if (AOB_ENAB(sih)) {
        pmu = si_setcore(sih, PMU_CORE_ID, 0);
    } else {
        pmu = si_setcoreidx(sih, SI_CC_IDX);
    }
    ASSERT(pmu != NULL);

    OR_REG(osh, &pmu->pmuintmask0, PMU_INT_STAT_TIMER_INT_MASK);

    /* Return to original core */
    si_setcoreidx(sih, origidx);
}

void si_pmustatstimer_int_disable(si_t *sih)
{
    pmuregs_t *pmu;
    uint origidx;
    osl_t *osh = si_osh(sih);

    /* Remember original core before switch to chipc/pmu */
    origidx = si_coreidx(sih);
    if (AOB_ENAB(sih)) {
        pmu = si_setcore(sih, PMU_CORE_ID, 0);
    } else {
        pmu = si_setcoreidx(sih, SI_CC_IDX);
    }
    ASSERT(pmu != NULL);

    AND_REG(osh, &pmu->pmuintmask0, ~PMU_INT_STAT_TIMER_INT_MASK);

    /* Return to original core */
    si_setcoreidx(sih, origidx);
}

void si_pmustatstimer_init(si_t *sih)
{
    pmuregs_t *pmu;
    uint origidx;
    osl_t *osh = si_osh(sih);
    uint32 core_cap_ext;
    uint8 max_stats_timer_num;
    int8 i;

    /* Remember original core before switch to chipc/pmu */
    origidx = si_coreidx(sih);
    if (AOB_ENAB(sih)) {
        pmu = si_setcore(sih, PMU_CORE_ID, 0);
    } else {
        pmu = si_setcoreidx(sih, SI_CC_IDX);
    }
    ASSERT(pmu != NULL);

    core_cap_ext = R_REG(osh, &pmu->core_cap_ext);

    max_stats_timer_num =
        ((core_cap_ext & PCAP_EXT_ST_NUM_MASK) >> PCAP_EXT_ST_NUM_SHIFT) + 1;

    for (i = 0; i < max_stats_timer_num; i++) {
        si_pmustatstimer_update(osh, pmu, i);
    }

    OR_REG(osh, &pmu->pmuintmask0, PMU_INT_STAT_TIMER_INT_MASK);

    /* Return to original core */
    si_setcoreidx(sih, origidx);
}

void si_pmustatstimer_dump(si_t *sih)
{
    pmuregs_t *pmu;
    uint origidx;
    osl_t *osh = si_osh(sih);
    uint32 core_cap_ext, pmucapabilities, AlpPeriod, ILPPeriod, pmuintmask0,
        pmuintstatus;
    uint8 max_stats_timer_num, max_stats_timer_src_num;
    uint32 stat_timer_ctrl, stat_timer_N;
    uint8 i;
    uint32 current_time_ms = OSL_SYSUPTIME();

    /* Remember original core before switch to chipc/pmu */
    origidx = si_coreidx(sih);
    if (AOB_ENAB(sih)) {
        pmu = si_setcore(sih, PMU_CORE_ID, 0);
    } else {
        pmu = si_setcoreidx(sih, SI_CC_IDX);
    }
    ASSERT(pmu != NULL);

    pmucapabilities = R_REG(osh, &pmu->pmucapabilities);
    core_cap_ext = R_REG(osh, &pmu->core_cap_ext);
    AlpPeriod = R_REG(osh, &pmu->slowclkperiod);
    ILPPeriod = R_REG(osh, &pmu->ILPPeriod);

    max_stats_timer_num =
        ((core_cap_ext & PCAP_EXT_ST_NUM_MASK) >> PCAP_EXT_ST_NUM_SHIFT) + 1;
    max_stats_timer_src_num = ((core_cap_ext & PCAP_EXT_ST_SRC_NUM_MASK) >>
                               PCAP_EXT_ST_SRC_NUM_SHIFT) +
                              1;

    pmuintstatus = R_REG(osh, &pmu->pmuintstatus);
    pmuintmask0 = R_REG(osh, &pmu->pmuintmask0);

    PMU_ERROR(("%s : TIME %d\n", __FUNCTION__, current_time_ms));

    PMU_ERROR(("\tMAX Timer Num %d, MAX Source Num %d\n", max_stats_timer_num,
               max_stats_timer_src_num));
    PMU_ERROR(("\tpmucapabilities 0x%8x, core_cap_ext 0x%8x, AlpPeriod 0x%8x, "
               "ILPPeriod 0x%8x, "
               "pmuintmask0 0x%8x, pmuintstatus 0x%8x, pmurev %d\n",
               pmucapabilities, core_cap_ext, AlpPeriod, ILPPeriod, pmuintmask0,
               pmuintstatus, PMUREV(sih->pmurev)));

    for (i = 0; i < max_stats_timer_num; i++) {
        W_REG(osh, &pmu->pmu_statstimer_addr, i);
        stat_timer_ctrl = R_REG(osh, &pmu->pmu_statstimer_ctrl);
        stat_timer_N = R_REG(osh, &pmu->pmu_statstimer_N);
        PMU_ERROR(("\t Timer %d : control 0x%8x, %d\n", i, stat_timer_ctrl,
                   stat_timer_N));
    }

    /* Return to original core */
    si_setcoreidx(sih, origidx);
}

void si_pmustatstimer_start(si_t *sih, uint8 timerid)
{
    pmuregs_t *pmu;
    uint origidx;
    osl_t *osh = si_osh(sih);

    /* Remember original core before switch to chipc/pmu */
    origidx = si_coreidx(sih);
    if (AOB_ENAB(sih)) {
        pmu = si_setcore(sih, PMU_CORE_ID, 0);
    } else {
        pmu = si_setcoreidx(sih, SI_CC_IDX);
    }
    ASSERT(pmu != NULL);

    pmustatstimer[timerid].enable = TRUE;

    W_REG(osh, &pmu->pmu_statstimer_addr, timerid);
    OR_REG(osh, &pmu->pmu_statstimer_ctrl, PMU_ST_ENAB << PMU_ST_EN_SHIFT);

    /* Return to original core */
    si_setcoreidx(sih, origidx);
}

void si_pmustatstimer_stop(si_t *sih, uint8 timerid)
{
    pmuregs_t *pmu;
    uint origidx;
    osl_t *osh = si_osh(sih);

    /* Remember original core before switch to chipc/pmu */
    origidx = si_coreidx(sih);
    if (AOB_ENAB(sih)) {
        pmu = si_setcore(sih, PMU_CORE_ID, 0);
    } else {
        pmu = si_setcoreidx(sih, SI_CC_IDX);
    }
    ASSERT(pmu != NULL);

    pmustatstimer[timerid].enable = FALSE;

    W_REG(osh, &pmu->pmu_statstimer_addr, timerid);
    AND_REG(osh, &pmu->pmu_statstimer_ctrl, ~(PMU_ST_ENAB << PMU_ST_EN_SHIFT));

    /* Return to original core */
    si_setcoreidx(sih, origidx);
}

void si_pmustatstimer_clear(si_t *sih, uint8 timerid)
{
    pmuregs_t *pmu;
    uint origidx;
    osl_t *osh = si_osh(sih);

    /* Remember original core before switch to chipc/pmu */
    origidx = si_coreidx(sih);
    if (AOB_ENAB(sih)) {
        pmu = si_setcore(sih, PMU_CORE_ID, 0);
    } else {
        pmu = si_setcoreidx(sih, SI_CC_IDX);
    }
    ASSERT(pmu != NULL);

    W_REG(osh, &pmu->pmu_statstimer_addr, timerid);
    W_REG(osh, &pmu->pmu_statstimer_N, 0);

    /* Return to original core */
    si_setcoreidx(sih, origidx);
}

void si_pmustatstimer_clear_overflow(si_t *sih)
{
    uint8 i;
    uint32 core_cap_ext;
    uint8 max_stats_timer_num;
    uint32 timerN;
    pmuregs_t *pmu;
    uint origidx;
    osl_t *osh = si_osh(sih);

    /* Remember original core before switch to chipc/pmu */
    origidx = si_coreidx(sih);
    if (AOB_ENAB(sih)) {
        pmu = si_setcore(sih, PMU_CORE_ID, 0);
    } else {
        pmu = si_setcoreidx(sih, SI_CC_IDX);
    }
    ASSERT(pmu != NULL);

    core_cap_ext = R_REG(osh, &pmu->core_cap_ext);
    max_stats_timer_num =
        ((core_cap_ext & PCAP_EXT_ST_NUM_MASK) >> PCAP_EXT_ST_NUM_SHIFT) + 1;

    for (i = 0; i < max_stats_timer_num; i++) {
        W_REG(osh, &pmu->pmu_statstimer_addr, i);
        timerN = R_REG(osh, &pmu->pmu_statstimer_N);
        if (timerN == 0xFFFFFFFF) {
            PMU_ERROR(("pmustatstimer overflow clear - timerid : %d\n", i));
            si_pmustatstimer_clear(sih, i);
        }
    }

    /* Return to original core */
    si_setcoreidx(sih, origidx);
}

uint32 si_pmustatstimer_read(si_t *sih, uint8 timerid)
{
    pmuregs_t *pmu;
    uint origidx;
    osl_t *osh = si_osh(sih);
    uint32 stats_timer_N;

    /* Remember original core before switch to chipc/pmu */
    origidx = si_coreidx(sih);
    if (AOB_ENAB(sih)) {
        pmu = si_setcore(sih, PMU_CORE_ID, 0);
    } else {
        pmu = si_setcoreidx(sih, SI_CC_IDX);
    }
    ASSERT(pmu != NULL);

    W_REG(osh, &pmu->pmu_statstimer_addr, timerid);
    stats_timer_N = R_REG(osh, &pmu->pmu_statstimer_N);

    /* Return to original core */
    si_setcoreidx(sih, origidx);

    return stats_timer_N;
}

void si_pmustatstimer_cfg_src_num(si_t *sih, uint8 src_num, uint8 timerid)
{
    pmuregs_t *pmu;
    uint origidx;
    osl_t *osh = si_osh(sih);

    /* Remember original core before switch to chipc/pmu */
    origidx = si_coreidx(sih);
    if (AOB_ENAB(sih)) {
        pmu = si_setcore(sih, PMU_CORE_ID, 0);
    } else {
        pmu = si_setcoreidx(sih, SI_CC_IDX);
    }
    ASSERT(pmu != NULL);

    pmustatstimer[timerid].src_num = src_num;
    si_pmustatstimer_update(osh, pmu, timerid);

    /* Return to original core */
    si_setcoreidx(sih, origidx);
}

void si_pmustatstimer_cfg_cnt_mode(si_t *sih, uint8 cnt_mode, uint8 timerid)
{
    pmuregs_t *pmu;
    uint origidx;
    osl_t *osh = si_osh(sih);

    /* Remember original core before switch to chipc/pmu */
    origidx = si_coreidx(sih);
    if (AOB_ENAB(sih)) {
        pmu = si_setcore(sih, PMU_CORE_ID, 0);
    } else {
        pmu = si_setcoreidx(sih, SI_CC_IDX);
    }
    ASSERT(pmu != NULL);

    pmustatstimer[timerid].cnt_mode = cnt_mode;
    si_pmustatstimer_update(osh, pmu, timerid);

    /* Return to original core */
    si_setcoreidx(sih, origidx);
}
#endif /* BCMPMU_STATS */
